#!/usr/bin/ruby
# -*- ruby -*-
#
# Spiral print the contents of a 2d array

require 'pp'
require 'test/unit'

class SpiralIterator
  include Enumerable

  DX_DY = [
           [0, 1],
           [1, 0],
           [0, -1],
           [-1, 0],
          ]

  def initialize(arr)
    @arr   = arr
  end

  def each
    coor = [0, 0]

    seen  = {}
    d_ind = 0

    until coor.nil?||
        @arr.nil? || @arr[coor.first].nil? || @arr[coor.first][coor.last].nil?
      # Hand the current position back to the caller
      yield @arr[coor.first][coor.last]

      # Mark that we've seen the current position
      seen[coor] = true

      # Get the next set of coordinates
      new_x = [coor.first + DX_DY[d_ind].first, 0].max
      new_y = [coor.last  + DX_DY[d_ind].last, 0].max

      # See if we *can't* advance to the next position. We can't if:
      # - We fall off the y axis
      # - We fall off the x axis
      # - We've seen the position already
      if @arr[new_x].nil? || @arr[new_x][new_y].nil? || seen[[new_x, new_y]]

        # Locate the next coordinate we can actually advance to
        d_ind = (d_ind + 1) % DX_DY.length
        
        new_x = [coor.first + DX_DY[d_ind].first, 0].max
        new_y = [coor.last  + DX_DY[d_ind].last, 0].max
      end

      # Try to advance to the next position. If we can, then awesome.
      # If we can't, then we've seen everything and can bail.
      coor = seen[[new_x, new_y]] ? nil : [new_x, new_y]
    end
  end

  def flatten
    return inject([]) { |r, v| r << v}
  end
end

class TestSpiralIterator < Test::Unit::TestCase
  def test_null_array
    assert_equal [], SpiralIterator.new(nil).flatten
    assert_equal [], SpiralIterator.new([]).flatten
    assert_equal [], SpiralIterator.new([[]]).flatten
  end

  def test_singleton
    assert_equal %w{00}, SpiralIterator.new([%w{00}]).flatten
  end

  def test_square_array_1
    arr = [
           %w{00 01 02 03},
           %w{11 12 13 04},
           %w{10 15 14 05},
           %w{09 08 07 06},
          ]

    exp = %w{00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15}

    assert_equal exp, SpiralIterator.new(arr).flatten
  end

  def test_square_array_1
    arr = [
           %w{00 01 02},
           %w{07 08 03},
           %w{06 05 04},
          ]

    exp = %w{00 01 02 03 04 05 06 07 08}

    assert_equal exp, SpiralIterator.new(arr).flatten
  end

  def test_rec_array_1
    arr = [
           %w{00 01 02 03},
           %w{09 10 11 04},
           %w{08 07 06 05},
          ]

    exp = %w{00 01 02 03 04 05 06 07 08 09 10 11}

    assert_equal exp, SpiralIterator.new(arr).flatten
  end

  def test_rec_array_2
    arr = [
           %w{00 01 02},
           %w{09 10 03},
           %w{08 11 04},
           %w{07 06 05},
          ]

    exp = %w{00 01 02 03 04 05 06 07 08 09 10 11}

    assert_equal exp, SpiralIterator.new(arr).flatten
  end
end
